package javaprocess

import (
	"fmt"
	"io/ioutil"
	"omniscient/internal/model/entity"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"

	"github.com/gogf/gf/v2/errors/gerror"
)

// JavaProcessInfo holds information about a Java process
type JavaProcessInfo struct {
	Name     string
	Pid      int
	Command  string
	Ports    []string
	JarDir   string
	IsDocker bool
}

// GetJavaProcesses returns a list of running Java processes
func GetJavaProcesses() ([]*entity.LinuxPid, error) {
	// 使用ps命令查找Java进程
	cmd := exec.Command("bash", "-c", "ps -ef | grep java")
	output, err := cmd.Output()
	if err != nil {
		return nil, gerror.New("执行命令出错: " + err.Error())
	}

	// 将输出转换为字符串并按行分割
	processes := strings.Split(string(output), "\n")
	result := make([]*entity.LinuxPid, 0)

	// 遍历并解析每个进程信息
	for _, process := range processes {
		// 跳过空行和grep进程本身
		if process == "" || strings.Contains(process, "grep java") {
			continue
		}

		if info := parseJavaProcess(process); info != nil {
			// 只保留包含-jar参数的真正Java项目进程
			if !containsJarParam(info.Command) {
				continue
			}

			if !info.IsDocker && len(info.Ports) == 0 {
				continue
			}

			// Docker值：1表示docker容器，2表示普通JDK进程
			dockerVal := 2
			if info.IsDocker {
				dockerVal = 1
				// 如果是Docker进程，获取容器名称作为项目名
				containerName := getDockerContainerName(info.Pid)
				if containerName != "" {
					info.Name = containerName
				}
			}

			linuxPid := &entity.LinuxPid{
				Name:    info.Name,
				Pid:     info.Pid,
				Run:     info.Command,
				Ports:   strings.Join(info.Ports, ","),
				Catalog: info.JarDir,
				Way:     dockerVal,
			}
			result = append(result, linuxPid)
		}
	}

	return result, nil
}

// 检查命令行是否包含-jar参数
func containsJarParam(command string) bool {
	return strings.Contains(command, "-jar")
}

// parseJavaProcess parses a single Java process line and returns process information
func parseJavaProcess(processLine string) *JavaProcessInfo {
	fields := strings.Fields(processLine)
	if len(fields) < 8 {
		return nil
	}

	pid, err := strconv.Atoi(fields[1])
	if err != nil {
		return nil
	}

	command := strings.Join(fields[7:], " ")
	name := extractJavaProjectName(command)

	if name == "unknown" {
		return nil
	}

	// 检查是否为Docker中的进程
	isDocker := checkIfDockerProcess(pid)

	ports := getTCPPorts(pid, isDocker)
	cmdLinePorts := extractPortFromCommand(command)
	allPorts := mergePorts(ports, cmdLinePorts)
	jarDir := getJarDirectory(pid, command)

	return &JavaProcessInfo{
		Name:     name,
		Pid:      pid,
		Command:  command,
		Ports:    allPorts,
		JarDir:   jarDir,
		IsDocker: isDocker,
	}
}

// getDockerContainerName retrieves the container name for a process running in Docker
func getDockerContainerName(pid int) string {
	// 读取进程的cgroup文件
	cgroupPath := fmt.Sprintf("/proc/%d/cgroup", pid)
	content, err := ioutil.ReadFile(cgroupPath)
	if err != nil {
		return ""
	}

	// 从cgroup内容中提取容器ID
	cgroupContent := string(content)
	var containerID string

	// 常见的Docker cgroup路径格式:
	// /docker/CONTAINER_ID
	// /system.slice/docker-CONTAINER_ID.scope
	// /docker-CONTAINER_ID.scope
	patterns := []string{
		`/docker/([a-zA-Z0-9]+)`,
		`/system\.slice/docker-([a-zA-Z0-9]+)\.scope`,
		`/docker-([a-zA-Z0-9]+)\.scope`,
	}

	for _, pattern := range patterns {
		re := regexp.MustCompile(pattern)
		matches := re.FindStringSubmatch(cgroupContent)
		if len(matches) > 1 {
			containerID = matches[1]
			break
		}
	}

	if containerID == "" {
		return ""
	}

	// 通过容器ID获取容器名称
	cmd := exec.Command("docker", "inspect", "--format", "{{.Name}}", containerID)
	output, err := cmd.Output()
	if err != nil {
		return ""
	}

	// 去除容器名称前的斜杠和末尾的换行符
	containerName := strings.TrimPrefix(strings.TrimSpace(string(output)), "/")
	return containerName
}

// checkIfDockerProcess checks if a process is running inside a Docker container
func checkIfDockerProcess(pid int) bool {
	cgroupPath := fmt.Sprintf("/proc/%d/cgroup", pid)

	content, err := ioutil.ReadFile(cgroupPath)
	if err != nil {
		return false
	}

	// 检查cgroup内容是否包含docker标识
	return strings.Contains(string(content), "docker")
}

// GetTCPPorts 返回一个进程所使用的所有 TCP 端口。
func getTCPPorts(pid int, isDocker bool) []string {

	if isDocker {
		// Docker容器：使用docker inspect
		return getDockerContainerPorts(pid)
	}

	// 首先尝试使用ss命令，只获取TCP端口
	cmd := exec.Command("bash", "-c", fmt.Sprintf("ss -tnlp | grep %d", pid))
	output, err := cmd.Output()

	// 如果ss命令失败，尝试使用netstat
	if err != nil {
		cmd = exec.Command("bash", "-c", fmt.Sprintf("netstat -tnlp | grep %d", pid))
		output, err = cmd.Output()
		// 如果netstat也失败，使用/proc文件系统作为保底方案
		if err != nil {
			return nil
		}
	}

	ports := parsePortsFromOutput(string(output))

	// 如果传统方法没有获取到端口，尝试/proc文件系统
	if len(ports) == 0 {
		return getTCPPortsFromProc(pid)
	}

	return ports
}

// 获取Docker容器的端口映射
func getDockerContainerPorts(pid int) []string {
	// 1. 获取容器ID
	containerID := getContainerID(pid)
	if containerID == "" {
		// 如果无法获取容器ID，尝试使用传统方法
		return getTCPPortsFromProc(pid)
	}

	// 2. 使用docker inspect获取端口映射
	cmd := exec.Command("docker", "inspect", "--format",
		"{{range $p, $conf := .NetworkSettings.Ports}}{{if $conf}}{{$p}} {{end}}{{end}}",
		containerID)
	output, err := cmd.Output()
	if err != nil {
		// docker inspect 失败，尝试备用方法
		// 可能是因为容器ID是短ID，尝试使用 docker ps 来匹配
		ports := getPortsFromDockerPS(containerID)
		if len(ports) > 0 {
			return ports
		}
		// 如果还是失败，使用 /proc 文件系统
		return getTCPPortsFromProc(pid)
	}

	// 3. 解析端口（格式如: 8080/tcp 9090/tcp）
	var ports []string
	portMap := make(map[string]bool)

	outputStr := strings.TrimSpace(string(output))
	if outputStr == "" {
		// 没有端口映射，尝试从容器内部获取监听端口
		return getContainerInternalPorts(pid)
	}

	for _, portStr := range strings.Fields(outputStr) {
		// 提取端口号，去掉 /tcp 或 /udp
		parts := strings.Split(portStr, "/")
		if len(parts) > 0 {
			port := parts[0]
			// 验证是否为数字
			if _, err := strconv.Atoi(port); err == nil {
				if !portMap[port] {
					ports = append(ports, port)
					portMap[port] = true
				}
			}
		}
	}

	return ports
}

// getPortsFromDockerPS 使用 docker ps 获取端口信息（作为备用方案）
func getPortsFromDockerPS(containerID string) []string {
	// 使用短ID的前12位
	shortID := containerID
	if len(containerID) > 12 {
		shortID = containerID[:12]
	}

	cmd := exec.Command("docker", "ps", "--filter", fmt.Sprintf("id=%s", shortID),
		"--format", "{{.Ports}}")
	output, err := cmd.Output()
	if err != nil {
		return nil
	}

	// 解析端口格式: 0.0.0.0:8080->8080/tcp, 0.0.0.0:9090->9090/tcp
	var ports []string
	portMap := make(map[string]bool)

	// 正则匹配端口号
	re := regexp.MustCompile(`->(\d+)/tcp`)
	matches := re.FindAllStringSubmatch(string(output), -1)

	for _, match := range matches {
		if len(match) > 1 {
			port := match[1]
			if !portMap[port] {
				ports = append(ports, port)
				portMap[port] = true
			}
		}
	}

	return ports
}

// getContainerInternalPorts 从容器内部获取监听端口（最后的备用方案）
func getContainerInternalPorts(pid int) []string {
	return getTCPPortsFromProc(pid)
}

// 从cgroup获取完整的容器ID
func getContainerID(pid int) string {
	cgroupPath := fmt.Sprintf("/proc/%d/cgroup", pid)
	content, err := ioutil.ReadFile(cgroupPath)
	if err != nil {
		return ""
	}

	cgroupContent := string(content)

	// 支持多种 cgroup 格式的正则表达式模式
	patterns := []string{
		// 完整64位容器ID
		`/docker/([a-f0-9]{64})`,
		`/docker-([a-f0-9]{64})\.scope`,
		`/system\.slice/docker-([a-f0-9]{64})\.scope`,

		// 短容器ID（12位或更短）
		`/docker/([a-f0-9]{12,})`,
		`/docker-([a-f0-9]{12,})\.scope`,
		`/system\.slice/docker-([a-f0-9]{12,})\.scope`,

		// cgroup v2 格式
		`docker-([a-f0-9]{12,})\.scope`,

		// Kubernetes pod 容器格式
		`/kubepods/[^/]+/pod[^/]+/([a-f0-9]{64})`,
		`/kubepods\.slice/[^/]+/([a-f0-9]{64})`,
	}

	for _, pattern := range patterns {
		re := regexp.MustCompile(pattern)
		matches := re.FindStringSubmatch(cgroupContent)
		if len(matches) > 1 {
			containerID := matches[1]
			// 验证提取的ID是否有效
			if len(containerID) >= 12 {
				return containerID
			}
		}
	}

	return ""
}

// parsePortsFromOutput parses ports from command output
func parsePortsFromOutput(output string) []string {
	var ports []string
	portMap := make(map[string]bool)

	lines := strings.Split(output, "\n")
	for _, line := range lines {
		if line == "" {
			continue
		}
		// ss输出格式: LISTEN 0 128 *:8080 *:* users:(("java",pid=1234,fd=10))
		// netstat输出格式: tcp 0 0 0.0.0.0:8080 0.0.0.0:* LISTEN 1234/java
		re := regexp.MustCompile(`[:\*](\d+)\s+(?:.*?)\s+LISTEN`)
		matches := re.FindStringSubmatch(line)
		if len(matches) > 1 {
			port := matches[1]
			if !portMap[port] {
				ports = append(ports, port)
				portMap[port] = true
			}
		}
	}

	return ports
}

// ExtractPortFromCommand 从 Java 命令行参数中提取端口号
func extractPortFromCommand(command string) []string {
	var ports []string
	portMap := make(map[string]bool)

	portPatterns := []string{
		`--server\.port=(\d+)`,
		`-Dserver\.port=(\d+)`,
		`port\s*=\s*(\d+)`,
		`-Dport=(\d+)`,
		`PORT=(\d+)`,
	}

	for _, pattern := range portPatterns {
		re := regexp.MustCompile(pattern)
		matches := re.FindStringSubmatch(command)
		if len(matches) > 1 {
			port := matches[1]
			if !portMap[port] {
				ports = append(ports, port)
				portMap[port] = true
			}
		}
	}

	return ports
}

// MergePorts merges and deduplicates two slices of port numbers
func mergePorts(ports1, ports2 []string) []string {
	portMap := make(map[string]bool)
	var result []string

	for _, port := range ports1 {
		if !portMap[port] {
			portMap[port] = true
			result = append(result, port)
		}
	}

	for _, port := range ports2 {
		if !portMap[port] {
			portMap[port] = true
			result = append(result, port)
		}
	}

	return result
}

// ExtractJavaProjectName extracts the project name from a Java command
func extractJavaProjectName(command string) string {
	if strings.Contains(command, "-jar") {
		parts := strings.Split(command, "-jar")
		if len(parts) > 1 {
			remaining := parts[1]
			for _, part := range strings.Fields(remaining) {
				if strings.HasSuffix(part, ".jar") {
					return part[strings.LastIndex(part, "/")+1:]
				}
			}
		}
	}
	return "unknown"
}

// GetJarDirectory returns the directory containing the JAR file
func getJarDirectory(pid int, command string) string {
	jarPath := extractJarPath(command)
	if jarPath == "" {
		return ""
	}

	if !filepath.IsAbs(jarPath) {
		if workDir := getProcessWorkDir(pid); workDir != "" {
			jarPath = filepath.Join(workDir, jarPath)
		}
	}

	return filepath.Dir(jarPath)
}

// getProcessWorkDir returns the working directory of a process
func getProcessWorkDir(pid int) string {
	// Try pwdx first
	cmd := exec.Command("bash", "-c", fmt.Sprintf("pwdx %d | cut -d: -f2", pid))
	output, err := cmd.Output()
	if err == nil {
		return strings.TrimSpace(string(output))
	}

	// Fall back to /proc/pid/cwd
	cmd = exec.Command("bash", "-c", fmt.Sprintf("readlink /proc/%d/cwd", pid))
	output, err = cmd.Output()
	if err == nil {
		return strings.TrimSpace(string(output))
	}

	return ""
}

// extractJarPath extracts the JAR file path from a Java command
func extractJarPath(command string) string {
	if strings.Contains(command, "-jar") {
		parts := strings.Split(command, "-jar")
		if len(parts) > 1 {
			remaining := parts[1]
			for _, part := range strings.Fields(remaining) {
				if strings.HasSuffix(part, ".jar") {
					return part
				}
			}
		}
	}
	return ""
}

// /proc文件系统保底方案
func getTCPPortsFromProc(pid int) []string {
	// 检查进程是否存在
	if !processExists(pid) {
		return nil
	}

	// 获取进程的socket inode列表
	inodes := getProcessSocketInodes(pid)
	if len(inodes) == 0 {
		return nil
	}

	// 从/proc/net/tcp和/proc/net/tcp6获取端口信息
	var ports []string
	ports = append(ports, getPortsFromTCPTable("/proc/net/tcp", inodes)...)
	ports = append(ports, getPortsFromTCPTable("/proc/net/tcp6", inodes)...)

	return removeDuplicates(ports)
}

func processExists(pid int) bool {
	_, err := os.Stat(fmt.Sprintf("/proc/%d", pid))
	return err == nil
}

func getProcessSocketInodes(pid int) []string {
	var inodes []string
	fdDir := fmt.Sprintf("/proc/%d/fd", pid)

	files, err := ioutil.ReadDir(fdDir)
	if err != nil {
		// 如果无法读取fd目录，可能是权限问题，返回空
		return nil
	}

	for _, file := range files {
		if file.Mode()&os.ModeSymlink != 0 {
			linkPath := filepath.Join(fdDir, file.Name())
			target, err := os.Readlink(linkPath)
			if err != nil {
				continue
			}

			// 检查是否是socket
			if strings.HasPrefix(target, "socket:[") {
				inode := strings.TrimPrefix(target, "socket:[")
				inode = strings.TrimSuffix(inode, "]")
				inodes = append(inodes, inode)
			}
		}
	}

	return inodes
}

func getPortsFromTCPTable(tcpFile string, targetInodes []string) []string {
	var ports []string

	content, err := ioutil.ReadFile(tcpFile)
	if err != nil {
		return nil
	}

	lines := strings.Split(string(content), "\n")
	for _, line := range lines[1:] { // 跳过头部
		fields := strings.Fields(line)
		if len(fields) < 10 {
			continue
		}

		// 检查状态是否为LISTEN (0A = 10 = LISTEN)
		state := fields[3]
		if state != "0A" {
			continue
		}

		// 检查inode是否匹配
		lineInode := fields[9]
		for _, inode := range targetInodes {
			if lineInode == inode {
				// 解析本地地址和端口
				localAddr := fields[1]
				if port := extractPortFromAddr(localAddr); port != "" {
					ports = append(ports, port)
				}
				break
			}
		}
	}

	return ports
}

func extractPortFromAddr(addr string) string {
	parts := strings.Split(addr, ":")
	if len(parts) == 2 {
		if portInt, err := strconv.ParseInt(parts[1], 16, 32); err == nil {
			return fmt.Sprintf("%d", portInt)
		}
	}
	return ""
}

func removeDuplicates(ports []string) []string {
	keys := make(map[string]bool)
	var result []string

	for _, port := range ports {
		if !keys[port] {
			keys[port] = true
			result = append(result, port)
		}
	}

	return result
}
